use yew::services::fetch::{FetchTask, Request, Response, FetchService};
use yew::Callback;
use yew::format::{Text, Nothing, Json};
use serde::{Deserialize, Serialize};
use crate::utils::state::{get_api, get_token, remove_token};
use crate::error::Error;
use crate::router::WebRouter;

#[derive(Default, Debug)]
pub struct Http;

impl Http {
    pub fn new() -> Self { Self }
    pub fn builder<T, B>(
        &mut self,
        method: &str,
        url: &str,
        body: B,
        done: Callback<Result<T, Error>>
    ) -> FetchTask
    where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug, B: Into<Text> {
        let url = if url == "/app.json" { url.to_string() }
        else { format!("{}{}", get_api().unwrap_or(String::new()), url) };

        let handler = Callback::from(move |res: Response<Text>| {
            if let (head, Ok(rlt)) = res.into_parts() {
                if head.status.is_success() {
                    let data: Result<T, _> = serde_json::from_str(&rlt);
                    if let Ok(dt) = data {
                        done.emit(Ok(dt));
                    } else {
                        let err: Result<crate::error::GraphqlError, _> = serde_json::from_str(&rlt);
                        if let Ok(e) = err {
                            done.emit(Err(Error::GraphqlError(e)))
                        } else {
                            done.emit(Err(Error::DeserializeError))
                        }
                    }
                } else {
                    match head.status.as_u16() {
                        401 => {
                            remove_token();
                            crate::utils::redirect::to(WebRouter::Login);
                        },
                        403 => done.emit(Err(Error::Forbidden)),
                        404 => done.emit(Err(Error::NotFound)),
                        500 => done.emit(Err(Error::InternalServerError)),
                        422 => done.emit(Err(Error::DeserializeError)),
                        _ => done.emit(Err(Error::RequestError)),
                    }
                }
            } else {
                remove_token();
                crate::utils::redirect::to(WebRouter::Login);
            }
        });

        let req = Request::builder()
            .uri(url)
            .method(method)
            .header("Content-Type", "application/json")
            .header("Authorization", get_token().unwrap_or(String::new()))
            .body(body).unwrap();

        FetchService::fetch(req, handler).unwrap()
    }

    pub fn get<T>(
        &mut self, url: &str,
        done: Callback<Result<T, Error>>
    ) -> FetchTask where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug {
        self.builder("GET", url, Nothing, done)
    }

    pub fn delete<T>(
        &mut self, url: &str,
        done: Callback<Result<T, Error>>
    ) -> FetchTask where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug {
        self.builder("DELETE", url, Nothing, done)
    }

    pub fn post<T, B>(
        &mut self, url: &str, body: B,
        done: Callback<Result<T, Error>>
    ) -> FetchTask
    where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug, B: Serialize + std::fmt::Debug {
        let body = Json(&body);
        self.builder("POST", url, body, done)
    }

    pub fn put<T, B>(
        &mut self, url: &str, body: B,
        done: Callback<Result<T, Error>>
    ) -> FetchTask
        where for<'de> T: Deserialize<'de> + 'static + std::fmt::Debug, B: Serialize + std::fmt::Debug {
        let body = Json(&body);
        self.builder("PUT", url, body, done)
    }
}